//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace TridentAPI
{
    using System;
    using System.Collections.Generic;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.DataLayer;

    /// <summary>
    /// 
    /// </summary>
    public class InstanceAPI
    {
        /// <summary>
        /// Global registry connection.
        /// </summary>
        private Connection registryConnection = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="InstanceAPI"/> class.
        /// </summary>
        /// <param name="regConnection">The reg connection.</param>
        public InstanceAPI(Connection regConnection)
        {
            registryConnection = regConnection;
        }

        /// <summary>
        /// Updates the activity instance parameters.
        /// </summary>
        /// <param name="job">The job.</param>
        /// <param name="param">The param.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public ActivityInstanceParameter CreateActivityInstanceParameters(Job job, ParameterAssignment parameter, object value)
        {
            //Check whether the registry connection is valid
            if (null == registryConnection || !registryConnection.Alive)
            {
                return null;
            }

            string actualVal = string.Empty;


            // First try to serialize with TypeInitializer.
            TypeProviderHelper.Initialize(registryConnection);

            TypeInitializer typeInit = null;
            Execute(() =>
            {
                typeInit = TypeProviderHelper.FetchTypeInitializer(parameter.ActivityParameter.Type, Microsoft.Research.DataLayer.Runtime.WPF);
            });

            if (typeInit != null)
            {
                Execute(() =>
                {
                    BaseTypeInitializer baseTypeInit = TypeProviderHelper.LoadInitializer(typeInit, string.Empty);
                    if (baseTypeInit != null)
                    {
                        actualVal = baseTypeInit.GetSerializedVersion(value);
                    }
                });
            }
            else
            {
                Execute(() =>
                {
                    actualVal = TypeProviderHelper.FetchSerializedValue(AssemblyLoad.SearchTypeIncurrentDomain(parameter.ActivityParameter.Type), value);
                });

                if (null != value && string.IsNullOrEmpty(actualVal))
                {
                    actualVal = value.ToString();
                }
            }

            ActivityInstanceParameter aiParam = null;
            Execute(() =>
            {
                //Associating the first activity instance right now we might require to change this if we add more activity instances per Job
                aiParam = ActivityInstanceParameter.Create(
                                                        actualVal,
                                                        job.ActivityInstances[0],
                                                        parameter,
                                                        registryConnection);
                aiParam.Save();
            });

            return aiParam;
        }

        /// <summary>
        /// Gets the unbound params.
        /// </summary>
        /// <param name="workflow">The workflow.</param>
        /// <returns></returns>
        public List<ParameterAssignment> GetUnboundParameters(Activity workflow)
        {
            List<ParameterAssignment> assignedParameters = new List<ParameterAssignment>();
            Execute(() =>
            {
                //Check whether the registry connection is valid
                if (null != registryConnection && registryConnection.Alive)
                {
                    InstanceAPI.GetUnboundParameters(workflow.BaseSequence, assignedParameters);
                }
            });

            return assignedParameters;
        }

        /// <summary>
        /// Gets the workflow required params for the specified workflow.
        /// </summary>
        /// <param name="workflow">The workflow.</param>
        /// <returns></returns>
        public List<ParameterAssignment> GetWorkflowRequiredParameters(Activity workflow)
        {
            List<ParameterAssignment> requiredParameters = new List<ParameterAssignment>();
            Execute(() =>
            {
                //Check whether the registry connection is valid
                if (null != registryConnection && registryConnection.Alive)
                {
                    InstanceAPI.GetRequiredParameters(workflow.BaseSequence, requiredParameters);
                }
            });

            return requiredParameters;
        }

        /// <summary>
        /// Gets the workflow output parameters for the specified workflow.
        /// </summary>
        /// <param name="workflow">The workflow.</param>
        /// <returns></returns>
        public List<ParameterAssignment> GetWorkflowOutputParameters(Activity workflow)
        {
            if (!workflow.IsWorkflow)
            {
                throw new ArgumentOutOfRangeException("workflow");
            }

            List<ParameterAssignment> outputParameters = new List<ParameterAssignment>();
            Execute(() =>
            {
                //Check whether the registry connection is valid
                if (registryConnection != null && registryConnection.Alive)
                {
                    InstanceAPI.GetOutputParameters(workflow.BaseSequence, outputParameters);
                }
            });

            return outputParameters;
        }

        /// <summary>
        /// Get the dataproducts for a specified job.
        /// </summary>
        /// <param name="job">The job.</param>
        /// <returns>The list of dataproducts created by the job.</returns>
        public List<DataProduct> GetDataProducts(Job job)
        {
            if (job == null)
            {
                throw new ArgumentNullException("job");
            }
            List<DataProduct> dataproducts = new List<DataProduct>();
            Execute(() =>
            {
                if (registryConnection != null && registryConnection.Alive)
                {
                    ActivityInstance activityInstance = job.ActivityInstances[0];
                    foreach (DataProduct dataproduct in activityInstance.Outputs)
                    {
                        if (dataproduct.CreatorParameter == null)
                        {
                            dataproducts.Add(dataproduct);
                        }
                    }
                }
            });
            return dataproducts;
        }

        /// <summary>
        /// Get the workflow outputs for the specified job.
        /// </summary>
        /// <param name="job">The Job.</param>
        /// <returns>The list of workflow outputs created by the job.</returns>
        public List<DataProduct> GetWorkflowOutputs(Job job)
        {
            List<DataProduct> dataproducts = new List<DataProduct>();
            Execute(() =>
            {
                if (registryConnection != null && registryConnection.Alive)
                {
                    ActivityInstance activityInstance = job.ActivityInstances[0];
                    foreach (DataProduct dataproduct in activityInstance.Outputs)
                    {
                        if (dataproduct.CreatorParameter != null && dataproduct.CreatorParameter.IsFinal)
                        {
                            dataproducts.Add(dataproduct);
                        }
                    }
                }
            });
            return dataproducts;
        }

        /// <summary>
        /// Creates a new job in the registry.
        /// </summary>
        /// <param name="jobName">the job name.</param>
        /// <param name="machine">The machine where the job needs to be executed.</param>
        /// <param name="workflow">The workflow to execute.</param>
        /// <returns>The job created.</returns>
        public Job CreateJob(string jobName, Machine machine, Activity workflow)
        {
            if (string.IsNullOrEmpty(jobName))
            {
                throw new ArgumentNullException("jobName");
            }
            if (machine == null)
            {
                throw new ArgumentNullException("machine");
            }
            if (workflow == null)
            {
                throw new ArgumentNullException("workflow");
            }
            Job newJob = null;
            Execute(() =>
            {
                ActivityInstance actInstance = ActivityInstance.Create(workflow, this.registryConnection);
                actInstance.Save();
                
                newJob = Job.Create(jobName, false, this.registryConnection);

                newJob.ScheduledBy = this.GetCurrentUser();
                newJob.Machines.Add(machine);
                newJob.ActivityInstances.Add(actInstance);
                newJob.Save();

                // Set this at the end. After all values are assigned.
                newJob.Status = JobStatus.Waiting;
                newJob.Save();
                newJob.Refresh();
            });
            return newJob;
        }

        /// <summary>
        /// Gets the unbounded paramters from the Activity sequence and then
        /// assign it to the List passed as the parameter.
        /// </summary>
        /// <param name="baseActivitySequence">The base activity sequence.</param>
        /// <param name="parameterAssignments">Collated parameter assignments.</param>
        private static void GetUnboundParameters(ActivitySequence baseActivitySequence, List<ParameterAssignment> parameterAssignments)
        {
            foreach (ParameterAssignment paramAssign in baseActivitySequence.ParameterAssignments)
            {
                if (paramAssign.ActivityParameter.Direction == DirectionType.Input && !paramAssign.IsBound)
                {
                    parameterAssignments.Add(paramAssign);
                }
            }

            if (baseActivitySequence.Activity.IsComposite || baseActivitySequence.Activity.IsWorkflow)
            {
                foreach (ActivitySequence child in baseActivitySequence.Children)
                {
                    InstanceAPI.GetUnboundParameters(child, parameterAssignments);
                }
            }
        }

        /// <summary>
        /// Get the workflow required parameters for the workflow
        /// </summary>
        /// <param name="baseActivitySequence">The base activity sequence.</param>
        /// <param name="parameterAssignments">The parameter assignments.</param>
        private static void GetRequiredParameters(ActivitySequence baseActivitySequence, List<ParameterAssignment> parameterAssignments)
        {
            foreach (ParameterAssignment paramAssign in baseActivitySequence.ParameterAssignments)
            {
                if (true == paramAssign.RequiredForWorkflow)
                {
                    parameterAssignments.Add(paramAssign);
                }
            }

            if (baseActivitySequence.Activity.IsComposite || baseActivitySequence.Activity.IsWorkflow)
            {
                foreach (ActivitySequence child in baseActivitySequence.Children)
                {
                    InstanceAPI.GetRequiredParameters(child, parameterAssignments);
                }
            }
        }

        /// <summary>
        /// Get the workflow Output parameters for the workflow.
        /// </summary>
        /// <param name="baseActivitySequence">The base activity sequence.</param>
        /// <param name="outputParameters">The output parameters.</param>
        private static void GetOutputParameters(ActivitySequence baseActivitySequence, List<ParameterAssignment> outputParameters)
        {
            foreach (ParameterAssignment paramAssign in baseActivitySequence.ParameterAssignments)
            {
                if (paramAssign.RequiredForWorkflow && paramAssign.ActivityParameter.Direction == DirectionType.Output)
                {
                    outputParameters.Add(paramAssign);
                }
            }

            if (baseActivitySequence.Activity.IsComposite || baseActivitySequence.Activity.IsWorkflow)
            {
                foreach (ActivitySequence child in baseActivitySequence.Children)
                {
                    InstanceAPI.GetOutputParameters(child, outputParameters);
                }
            }
        }

        /// <summary>
        /// Get the current user from the registry.
        /// </summary>
        /// <returns>The current user.</returns>
        private User GetCurrentUser()
        {
            string user = TridentAuthentication.LoggedUserInUserName;//Environment.UserName;
            return User.Load(user, this.registryConnection);
        }

        /// <summary>
        /// Executes the <paramref name="action"/> object inside the exception block.
        /// </summary>
        /// <param name="action">Instance of the Action.</param>
        private static void Execute(System.Action action)
        {
            try
            {
                action();
            }
            catch (Exception exp)
            {
                TridentCustomException custExp = new TridentCustomException(exp.Message, exp);
                throw custExp;
            }
        }
    }
}
